from fastapi import APIRouter, Depends, Query, UploadFile, File, Body, HTTPException, Form
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, delete, update
from app.db.postgres import get_session
from app.models import IndicatorVersion, IndicatorDimension, IndicatorTheme, IndicatorItem, IndicatorVersionActivityType, DictionaryItem
from app.core.security import get_current_user
from app.services.codes import allocate_codes, ensure_counter
import io
from openpyxl import load_workbook
import logging

logger = logging.getLogger("indicators")


router = APIRouter(prefix="/indicators", tags=["indicators"])


@router.get("/versions")
async def list_versions(session: AsyncSession = Depends(get_session)):
    result = await session.execute(select(IndicatorVersion))
    versions = result.scalars().all()
    # 拉取每个版本的适用活动类型
    vt_res = await session.execute(
        select(IndicatorVersionActivityType.version_id, DictionaryItem.id, DictionaryItem.code, DictionaryItem.name)
        .join(DictionaryItem, IndicatorVersionActivityType.activity_type_id == DictionaryItem.id)
    )
    vt_map: dict[str, list[dict]] = {}
    for vid, did, dcode, dname in vt_res.fetchall():
        k = str(vid)
        vt_map.setdefault(k, []).append({"id": str(did), "code": dcode, "name": dname})
    return [
        {
            "id": str(x.id),
            "version_code": x.version_code,
            "name": x.name,
            "source_doc": x.source_doc,
            "created_at": x.created_at,
            "activity_types": vt_map.get(str(x.id), []),
        }
        for x in versions
    ]


@router.post("/versions")
async def create_version(payload: dict = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    version_code = payload.get("version_code")
    name = payload.get("name")
    source_doc = payload.get("source_doc")
    if not version_code or not name:
        raise HTTPException(status_code=400, detail="version_code_and_name_required")
    obj = IndicatorVersion(version_code=version_code, name=name, source_doc=source_doc)
    session.add(obj)
    await session.flush()
    await session.commit()
    return {"id": str(obj.id), "version_code": obj.version_code, "name": obj.name, "source_doc": obj.source_doc}


@router.get("/dimensions")
async def list_dimensions(version_id: str = Query(...), session: AsyncSession = Depends(get_session)):
    # 获取所有维度
    result = await session.execute(select(IndicatorDimension).where(IndicatorDimension.version_id == version_id))
    dimensions = result.scalars().all()
    
    # 获取每个维度的聚合分数
    dimension_data = []
    for dim in dimensions:
        # 获取该维度下所有主题的ID
        theme_result = await session.execute(
            select(IndicatorTheme.id).where(IndicatorTheme.dimension_id == dim.id)
        )
        theme_ids = [str(t[0]) for t in theme_result.fetchall()]
        
        if theme_ids:
            # 获取所有三级指标的分数
            score_result = await session.execute(
                select(IndicatorItem.weight, IndicatorItem.deduct_value, IndicatorItem.category)
                .where(IndicatorItem.theme_id.in_(theme_ids))
            )
            
            total_weight = 0
            total_deduct = 0
            for weight, deduct_value, category in score_result.fetchall():
                if category == 'basic' and weight is not None:
                    total_weight += float(weight)
                elif category == 'deduct' and deduct_value is not None:
                    total_deduct += float(deduct_value)
        else:
            total_weight = 0
            total_deduct = 0
            
        dimension_data.append({
            "id": str(dim.id),
            "version_id": str(dim.version_id),
            "name": dim.name,
            "order_index": dim.order_index,
            "aggregated_weight": total_weight,
            "aggregated_deduct": total_deduct,
        })
    
    return dimension_data


@router.get("/themes")
async def list_themes(dimension_id: str = Query(...), session: AsyncSession = Depends(get_session)):
    # 获取所有主题
    result = await session.execute(select(IndicatorTheme).where(IndicatorTheme.dimension_id == dimension_id))
    themes = result.scalars().all()
    
    # 获取每个主题的聚合分数
    theme_data = []
    for theme in themes:
        # 获取该主题下所有三级指标的分数
        score_result = await session.execute(
            select(IndicatorItem.weight, IndicatorItem.deduct_value, IndicatorItem.category)
            .where(IndicatorItem.theme_id == theme.id)
        )
        
        total_weight = 0
        total_deduct = 0
        for weight, deduct_value, category in score_result.fetchall():
            if category == 'basic' and weight is not None:
                total_weight += float(weight)
            elif category == 'deduct' and deduct_value is not None:
                total_deduct += float(deduct_value)
        
        theme_data.append({
            "id": str(theme.id),
            "dimension_id": str(theme.dimension_id),
            "name": theme.name,
            "order_index": theme.order_index,
            "aggregated_weight": total_weight,
            "aggregated_deduct": total_deduct,
        })
    
    return theme_data


@router.get("/items")
async def list_items(theme_id: str = Query(...), session: AsyncSession = Depends(get_session)):
    result = await session.execute(select(IndicatorItem).where(IndicatorItem.theme_id == theme_id))
    return [
        {
            "id": str(x.id),
            "theme_id": str(x.theme_id),
            "name": x.name,
            "category": str(x.category),
            "weight": float(x.weight) if x.weight is not None else None,
            "deduct_value": float(x.deduct_value) if x.deduct_value is not None else None,
            "veto_flag": bool(x.veto_flag),
            "order_index": x.order_index,
            "code": x.code,
            "criteria": x.criteria,
        }
        for x in result.scalars().all()
    ]


def _norm_category(val: str) -> str:
    s = (val or '').strip()
    if s in ('基本','基本项','basic'): return 'basic'
    if s in ('扣分','扣分项','deduct'): return 'deduct'
    if s in ('否决','否决项','veto'): return 'veto'
    return 'basic'

def _clean_indicator_name(name: str) -> str:
    """清理指标名称，去除括号及里面的分数信息"""
    if not name:
        return ''
    import re
    # 移除括号及里面的内容，包括中文括号
    cleaned = re.sub(r'[（(][^）)]*[）)]', '', name.strip())
    return cleaned.strip()

def _safe_float(val, field_name: str, row_index: int, errors: list):
    if val in (None, ''):
        return None
    try:
        # 如果是字符串，尝试从中提取数字（处理中文文本中的数字）
        if isinstance(val, str):
            import re
            # 查找字符串中的第一个数字（支持小数）
            match = re.search(r'-?\d+(?:\.\d+)?', val.strip())
            if match:
                return float(match.group())
        # 直接转换为浮点数
        return float(val)
    except Exception:
        errors.append({"row": row_index, "field": field_name, "error": "invalid_number", "value": val})
        return None

def _safe_int(val, default: int, field_name: str, row_index: int, errors: list):
    if val in (None, ''):
        return default
    try:
        # Excel可能给浮点，取整
        return int(float(val))
    except Exception:
        errors.append({"row": row_index, "field": field_name, "error": "invalid_integer", "value": val})
        return default

def _to_bool(val) -> bool:
    if isinstance(val, bool): return val
    s = str(val or '').strip().lower()
    return s in ('1','true','是','y','yes')


@router.post("/import/test")
async def import_test(
    session: AsyncSession = Depends(get_session),
    user=Depends(get_current_user),
):
    print("=== IMPORT TEST 端点被调用 ===")
    logger.info("=== IMPORT TEST 端点被调用 ===")
    logger.info(f"用户认证信息: {user}")
    logging.getLogger().info("=== 根日志记录器测试 ===")
    return {"message": "test endpoint working"}


@router.post("/import/upload")
async def import_upload(
    version_id: Optional[str] = Query(None),
    dry_run: Optional[bool] = Query(None),
    file: UploadFile = File(...),
    version_id_form: Optional[str] = Form(None, alias="version_id"),
    dry_run_form: Optional[bool] = Form(None, alias="dry_run"),
    session: AsyncSession = Depends(get_session),
    user=Depends(get_current_user),
):
    logger.info(f"[import_upload] === 接收到上传请求 ===")
    logger.info(f"[import_upload] version_id_query: {version_id}, version_id_form: {version_id_form}, dry_run_query: {dry_run}, dry_run_form: {dry_run_form}")
    logger.info(f"[import_upload] 用户认证: {user}")
    logger.info(f"[import_upload] 请求文件: {file.filename}, 大小: {file.size}")
    
    vid = version_id or version_id_form
    dr = dry_run if dry_run is not None else (dry_run_form if dry_run_form is not None else True)
    
    logger.info(f"[import_upload] 处理参数 - vid: {vid}, dr: {dr}")
    
    if not vid:
        logger.error("[import_upload] 缺少version_id参数")
        raise HTTPException(status_code=400, detail="version_id_required")
    data = await file.read()
    logger.info(f"[import] filename={file.filename} size={len(data)} vid={vid} dry_run={dr}")
    wb = load_workbook(filename=io.BytesIO(data), read_only=False, data_only=True)
    ws = wb.worksheets[0]
    
    # 获取合并单元格信息 - 使用非只读模式支持合并单元格访问
    merged_cells = {}
    for merged_range in ws.merged_cells:
        min_col, min_row, max_col, max_row = merged_range.bounds
        # 获取合并区域左上角的值
        cell_value = ws.cell(row=min_row, column=min_col).value
        # 为合并区域内的所有单元格存储相同的值
        for row in range(min_row, max_row + 1):
            for col in range(min_col, max_col + 1):
                merged_cells[(row, col)] = cell_value
    
    # 新的解析逻辑：处理交替的指标类型和指标数据
    rows = list(ws.iter_rows(values_only=True))
    logger.info(f"[import] 总行数: {len(rows)}, 合并单元格数: {len(merged_cells)}")
    
    if len(rows) == 0:
        logger.error("[import] Excel文件为空")
        raise HTTPException(status_code=400, detail="empty_excel_file")
    
    # 分析文件结构 - 寻找真正的表头
    # 用户上传的文件结构：第1行是说明，需要找到包含实际列名的行
    header_row_index = None
    
    # 查找包含实际列名的行
    for i, row in enumerate(rows):
        if not row:
            continue
        # 检查这一行是否包含我们期望的列名
        row_text = ' '.join(str(cell or '') for cell in row)
        logger.info(f"[import] 检查第{i+1}行: {row_text[:100]}...")
        if any(key in row_text for key in ['序号', '一级指标', '二级指标', '三级指标', '赋值']):
            header_row_index = i
            logger.info(f"[import] 找到表头在第{i+1}行")
            break
    
    if header_row_index is None:
        logger.error("[import] 未找到有效的表头行")
        raise HTTPException(status_code=400, detail="header_not_found")
    
    if len(rows) <= header_row_index + 1:
        logger.error("[import] Excel文件行数不足")
        raise HTTPException(status_code=400, detail="insufficient_rows")
    
    headers = [str(h or '').strip() for h in rows[header_row_index]]
    logger.info(f"[import] 找到表头（第{header_row_index+1}行）: {headers}")
    
    # 检查表头是否有效
    valid_headers = any(key in headers for key in ['序号', '一级指标', '二级指标', '三级指标'])
    if not valid_headers:
        logger.error(f"[import] 无效的表头结构: {headers}")
        raise HTTPException(status_code=400, detail="invalid_header_structure")
    
    # 解析交替的指标类型和指标数据
    mapped = []
    current_category = None
    row_index = 1  # 从第二行开始（第一行是表头）
    
    # 用于跟踪当前的一二级指标
    current_dimension = None
    current_theme = None
    
    # 用于跟踪各层级的排序（从1开始）
    dimension_order_map = {}  # 维度层级排序
    theme_order_map = {}    # 主题层级排序  
    item_order_map = {}     # 指标项层级排序
    
    # 从表头下一行开始解析数据
    data_start_row = header_row_index + 1  # 从表头下一行开始
    
    for i in range(data_start_row, len(rows)):
        row = rows[i]
        row_index = i + 1
        
        # 检查这一行是否是指标类型行
        first_cell = str(row[0] or '').strip() if row else ''
        
        if first_cell in ['基本项', '扣分项', '一票否决项']:
            # 这是指标类型行
            if first_cell == '基本项':
                current_category = 'basic'
            elif first_cell == '扣分项':
                current_category = 'deduct'
            elif first_cell == '一票否决项':
                current_category = 'veto'
            
            logger.info(f"[import] 行 {row_index}: 发现指标类型 '{first_cell}' -> {current_category}")
            continue
        
        # 如果没有设置当前指标类型，跳过
        if not current_category:
            logger.info(f"[import] 行 {row_index}: 跳过 - 未找到对应的指标类型")
            continue
            
        # 解析指标数据行 - 处理合并单元格
        rec = {}
        for j, v in enumerate(row):
            # 如果当前单元格在合并区域内，使用合并的值
            if (row_index, j + 1) in merged_cells:
                cell_value = merged_cells[(row_index, j + 1)]
            else:
                cell_value = v
            key = headers[j] if j < len(headers) else f"c{j}"
            rec[key] = cell_value
        
        # 使用别名映射获取字段 - 根据实际表头结构调整
        def pick(r: dict, names: list[str]):
            for n in names:
                if n in r: return r.get(n)
            return None
        
        # 根据实际表头映射字段 - 使用正确的列索引
        seq_num = pick(rec, ['序号'])  # 序号
        dname = pick(rec, ['一级指标'])  # 一级指标
        tname = pick(rec, ['二级指标'])  # 二级指标
        iname = pick(rec, ['三级指标'])  # 三级指标
        criteria = pick(rec, [
            '评定标准说明',
            '赋值及评定标准说明',
            '赋值与评定标准说明',
            '赋值&评定标准说明',
            '评定标准',
            '说明'
        ])  # 评定标准说明
        value = pick(rec, ['赋值', '分值', '分数', '得分', '赋值及评定标准说明'])  # 赋值（分值）
        if not criteria and isinstance(value, str):
            criteria = value
        
        # 更新当前的一二级指标（处理合并单元格）
        if dname:
            current_dimension = _clean_indicator_name(str(dname))
            # 维度层级排序递增
            if current_dimension not in dimension_order_map:
                dimension_order_map[current_dimension] = len(dimension_order_map) + 1
        if tname:
            current_theme = _clean_indicator_name(str(tname))
            # 主题层级排序递增（在每个维度内）
            theme_key = f"{current_dimension}:{current_theme}"
            if theme_key not in theme_order_map:
                theme_order_map[theme_key] = len([k for k in theme_order_map.keys() if k.startswith(f"{current_dimension}:")]) + 1
        
        # 如果没有三级指标名称，跳过
        if not iname:
            logger.info(f"[import] 行 {row_index}: 跳过 - 缺少三级指标名称")
            continue
        
        # 确保有一二级指标
        if not current_dimension:
            logger.warning(f"[import] 行 {row_index}: 未找到一级指标，使用默认值")
            current_dimension = '默认维度'
        if not current_theme:
            logger.warning(f"[import] 行 {row_index}: 未找到二级指标，使用默认值")
            current_theme = '默认主题'
        
        # 只处理三级指标（有具体指标名称的）
        # 三级指标排序（在每个主题内）
        item_key = f"{current_dimension}:{current_theme}"
        if item_key not in item_order_map:
            item_order_map[item_key] = 0
        item_order_map[item_key] += 1
        current_item_order = item_order_map[item_key]
        
        rec_m = {
            'dimension_name': current_dimension,
            'theme_name': current_theme,
            'item_name': str(iname).strip(),
            'category': current_category,
            'weight': None,
            'deduct_value': None,
            'veto_flag': (current_category == 'veto'),
            'order_index': current_item_order,  # 使用层级内排序
            'dimension_order': dimension_order_map.get(current_dimension, 1),
            'theme_order': theme_order_map.get(f"{current_dimension}:{current_theme}", 1),
            'code': None,  # 代码将在后续分配
            'row_index': row_index,
            'criteria': (str(criteria).strip() if criteria else None),
        }
        
        # 根据分类设置权重或扣分值（仅三级指标需要）
        errors = []
        if current_category == 'basic':
            # 基本项：一级和二级指标不需要分数，分数只存在于三级指标的"赋值"列
            # 只有当有三级指标名称时，才从"赋值"列获取分数
            if iname and value:
                rec_m['weight'] = _safe_float(value, 'weight', rec_m['row_index'], errors)
            else:
                rec_m['weight'] = None  # 一级和二级指标不设置分数
        elif current_category == 'deduct':
            # 扣分项：一级和二级指标不需要分数，分数只存在于三级指标的"赋值"列
            if iname and value:
                rec_m['deduct_value'] = _safe_float(value, 'deduct_value', rec_m['row_index'], errors)
            else:
                rec_m['deduct_value'] = None  # 一级和二级指标不设置分数
        elif current_category == 'veto':
            # 一票否决项：权重设为0，表示否决项
            rec_m['weight'] = 0.0
            
        rec_m['order_index'] = _safe_int(rec_m['order_index'], 0, 'order_index', rec_m['row_index'], errors)
        mapped.append(rec_m)
        logger.info(f"[import] 行 {row_index}: 解析成功 - {rec_m['item_name']} ({current_category}) - 权重: {rec_m['weight']}, 扣分: {rec_m['deduct_value']}")
    
    logger.info(f"[import] 解析完成 - 总指标数: {len(mapped)}")
    # 校验编码合法与重复
    errors = []
    codes = [m['code'] for m in mapped if m['code']]
    # 长度与字符校验
    for m in mapped:
        c = m['code']
        if c and (len(c) != 4 or any(ch not in '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' for ch in c)):
            errors.append({"row": m['row_index'], "field": "code", "error": "invalid_code_format", "value": c})
    # Excel 内重复
    seen = {}
    for m in mapped:
        c = m['code']
        if not c: continue
        if c in seen:
            errors.append({"row": m['row_index'], "field": "code", "error": "duplicate_code_excel", "value": c})
        seen[c] = True
    # 数据库重复
    if codes:
        result = await session.execute(select(IndicatorItem.code).where(IndicatorItem.code.in_(codes)))
        existing = [x for x in result.scalars().all() if x]
        for c in existing:
            errors.append({"row": None, "field": "code", "error": "duplicate_code_db", "value": c})
    if dr:
        logger.info(f"[import] dry_run total={len(mapped)} errors={len(errors)}")
        return {"total": len(mapped), "errors": errors}
    await session.execute(delete(IndicatorDimension).where(IndicatorDimension.version_id == vid))
    dims = {}
    themes = {}
    if errors:
        logger.warning(f"[import] errors={errors}")
        raise HTTPException(status_code=400, detail={"errors": errors})
    need_codes = len([1 for rec in mapped if not rec['code']])
    codes_alloc = []
    if need_codes > 0:
        codes_alloc = await allocate_codes(session, 'indicator_item', need_codes, 4)
        logger.info(f"[import] allocated_codes={codes_alloc}")
    logger.info(f"[import] need_codes={need_codes} codes_alloc_length={len(codes_alloc)}")
    ci = 0
    dim_seq = {}
    theme_seq = {}
    for rec in mapped:
        dn = rec['dimension_name']
        tn = rec['theme_name']
        if dn not in dims:
            idx = dim_seq.get(dn, len(dim_seq))
            obj = IndicatorDimension(version_id=vid, name=dn, order_index=idx)
            session.add(obj)
            await session.flush()
            dims[dn] = str(obj.id)
            dim_seq[dn] = idx
        did = dims[dn]
        key = f"{did}:{tn}"
        if key not in themes:
            idx = theme_seq.get(key, len([k for k in theme_seq if k.startswith(did+':')]))
            obj = IndicatorTheme(dimension_id=did, name=tn, order_index=idx)
            session.add(obj)
            await session.flush()
            themes[key] = str(obj.id)
            theme_seq[key] = idx
        tid = themes[key]
        code = rec['code'] or (codes_alloc[ci] if ci < len(codes_alloc) else None)
        if not rec['code'] and ci < len(codes_alloc):
            ci += 1
        item = IndicatorItem(
            theme_id=tid, 
            name=rec['item_name'], 
            category=rec['category'], 
            weight=rec['weight'], 
            deduct_value=rec['deduct_value'], 
            veto_flag=rec['veto_flag'], 
            order_index=rec['order_index'], 
            code=code,
            criteria=rec.get('criteria')
        )
        session.add(item)
    await session.flush()
    # 记录版本来源文件名
    await session.execute(update(IndicatorVersion).where(IndicatorVersion.id == vid).values(source_doc=file.filename))
    await session.commit()
    logger.info(f"[import] done imported={len(mapped)} allocated_codes={need_codes}")
    return {"imported": len(mapped), "allocated_codes": need_codes}


@router.post("/items/codes/allocate")
async def allocate_item_codes(count: int = Query(..., gt=0), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    try:
        codes = await allocate_codes(session, 'indicator_item', count, 4)
        await session.commit()
        return {"codes": codes}
    except ValueError:
        raise HTTPException(status_code=400, detail="code_exhausted")


@router.post("/items/{item_id}/code")
async def assign_item_code(item_id: str, session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    try:
        codes = await allocate_codes(session, 'indicator_item', 1, 4)
    except ValueError:
        raise HTTPException(status_code=400, detail="code_exhausted")
    code = codes[0]
    await session.execute(update(IndicatorItem).where(IndicatorItem.id == item_id).values(code=code))
    await session.flush()
    await session.commit()
    return {"code": code}


@router.post("/dimensions")
async def create_dimension(payload: dict = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    version_id = payload.get("version_id")
    name = payload.get("name")
    order_index = int(payload.get("order_index") or 0)
    if not version_id or not name:
        raise HTTPException(status_code=400, detail="required")
    obj = IndicatorDimension(version_id=version_id, name=name, order_index=order_index)
    session.add(obj)
    await session.flush()
    await session.commit()
    return {"id": str(obj.id)}


@router.post("/themes")
async def create_theme(payload: dict = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    dimension_id = payload.get("dimension_id")
    name = payload.get("name")
    order_index = int(payload.get("order_index") or 0)
    if not dimension_id or not name:
        raise HTTPException(status_code=400, detail="required")
    obj = IndicatorTheme(dimension_id=dimension_id, name=name, order_index=order_index)
    session.add(obj)
    await session.flush()
    await session.commit()
    return {"id": str(obj.id)}


@router.post("/items")
async def create_item(payload: dict = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    theme_id = payload.get("theme_id")
    name = payload.get("name")
    category = payload.get("category")
    veto_flag = bool(payload.get("veto_flag") or False)
    order_index = int(payload.get("order_index") or 0)
    weight = payload.get("weight")
    deduct_value = payload.get("deduct_value")
    code = payload.get("code")
    criteria = payload.get("criteria")
    if not theme_id or not name or not category:
        raise HTTPException(status_code=400, detail="required")
    obj = IndicatorItem(theme_id=theme_id, name=name, category=category, weight=weight, deduct_value=deduct_value, veto_flag=veto_flag, order_index=order_index, code=code, criteria=criteria)
    session.add(obj)
    await session.flush()
    await session.commit()
    return {"id": str(obj.id)}


@router.patch("/versions/{version_id}")
async def update_version(version_id: str, payload: dict = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    logger.info(f"[update_version] 收到更新请求 - version_id: {version_id}, payload: {payload}, user: {user}")
    data = {}
    if 'version_code' in payload:
        data['version_code'] = payload['version_code']
    if 'name' in payload:
        data['name'] = payload['name']
    if 'source_doc' in payload:
        data['source_doc'] = payload['source_doc']
    try:
        if data:
            logger.info(f"[update_version] 更新数据 - version_id: {version_id}, data: {data}")
            await session.execute(update(IndicatorVersion).where(IndicatorVersion.id == version_id).values(**data))
        # 处理适用活动类型集合
        if 'activity_type_ids' in payload and isinstance(payload['activity_type_ids'], list):
            ids = [str(x) for x in payload['activity_type_ids'] if x]
            # 清空旧关联
            await session.execute(delete(IndicatorVersionActivityType).where(IndicatorVersionActivityType.version_id == version_id))
            # 重新插入
            for aid in ids:
                session.add(IndicatorVersionActivityType(version_id=version_id, activity_type_id=aid))
        await session.commit()
        return {"ok": True}
    except Exception as e:
        logger.error(f"[update_version] 更新失败 - version_id: {version_id}, 错误: {e}")
        await session.rollback()
        raise HTTPException(status_code=400, detail=f"更新版本失败: {str(e)}")


@router.patch("/{type}/{id}")
async def update_entity(type: str, id: str, payload: dict = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    if type == 'dimension':
        await session.execute(update(IndicatorDimension).where(IndicatorDimension.id == id).values(**payload))
    elif type == 'theme':
        await session.execute(update(IndicatorTheme).where(IndicatorTheme.id == id).values(**payload))
    elif type == 'item':
        await session.execute(update(IndicatorItem).where(IndicatorItem.id == id).values(**payload))
    else:
        raise HTTPException(status_code=400, detail="type_invalid")
    await session.flush()
    await session.commit()
    return {"ok": True}


@router.delete("/{type}/{id}")
async def delete_entity(type: str, id: str, session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    if type == 'dimension':
        await session.execute(delete(IndicatorDimension).where(IndicatorDimension.id == id))
    elif type == 'theme':
        await session.execute(delete(IndicatorTheme).where(IndicatorTheme.id == id))
    elif type == 'item':
        await session.execute(delete(IndicatorItem).where(IndicatorItem.id == id))
    else:
        raise HTTPException(status_code=400, detail="type_invalid")
    await session.flush()
    await session.commit()
    return {"ok": True}


@router.patch("/dimensions/order")
async def reorder_dimensions(version_id: str = Body(...), ids: list[str] = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    for idx, did in enumerate(ids):
        await session.execute(update(IndicatorDimension).where(IndicatorDimension.id == did).values(order_index=idx))
    await session.flush()
    await session.commit()
    return {"ok": True}


@router.patch("/themes/{dimension_id}/order")
async def reorder_themes(dimension_id: str, ids: list[str] = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    for idx, tid in enumerate(ids):
        await session.execute(update(IndicatorTheme).where(IndicatorTheme.id == tid).values(order_index=idx))
    await session.flush()
    await session.commit()
    return {"ok": True}


@router.patch("/items/{theme_id}/order")
async def reorder_items(theme_id: str, ids: list[str] = Body(...), session: AsyncSession = Depends(get_session), user=Depends(get_current_user)):
    for idx, iid in enumerate(ids):
        await session.execute(update(IndicatorItem).where(IndicatorItem.id == iid).values(order_index=idx))
    await session.flush()
    await session.commit()
    return {"ok": True}
